package com.lianqi.emcpframework.core.bean;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.springframework.core.ResolvableType;
import org.springframework.util.ReflectionUtils;

import lombok.Getter;
import lombok.Setter;

/**
 * @program emcp-framework
 * @ClassName Property
 * @description: 类属性包装类
 * @author: sky
 * @create: 2018/11/23 16:29
 */
@Getter
@Setter
public abstract class Property {
    String name;
    String readMethodName;
    String writeMethodName;

    BiFunction<Class, Class, String> getter;

    BiFunction<Class, String, String> setter;
    Class type;
    Class beanType;

    public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

    /**
     * 获取读方法定义
     * 
     * @return
     */
    public String getReadMethod() {
        return readMethodName + "()";
    }

    /**
     * 获取类型定义
     * 
     * @param name
     *            变量名
     * @return 类型 变量名
     */
    public String generateVar(String name) {
        return getTypeName().concat(" ").concat(name);
    }

    /**
     * 获取类型名
     * 
     * @return
     */
    public String getTypeName() {
        return getTypeName(type);
    }

    /**
     * 获取指定类的类型定义
     * 
     * @param type
     *            指定类
     * @return 类型定义
     */
    private String getTypeName(Class type) {
        String typeName = type.getName();
        if (type.isArray()) {
            typeName = type.getComponentType().getName() + "[]";
        }
        return typeName;
    }

    /**
     * 判断是否基础类型
     * 
     * @return
     */
    public boolean isPrimitive() {
        return isPrimitive(getType());
    }

    /**
     * 是否原始类型 8大基础类型
     * 
     * @param type
     *            指定类
     * @return
     */
    private boolean isPrimitive(Class type) {
        return type.isPrimitive();
    }

    /**
     * 判断是否为包装类型
     * 
     * @return
     */
    public boolean isWrapper() {
        return isWrapper(getType());
    }

    /**
     * 判断是否为包装类型
     * 
     * @param type
     *            指定类
     * @return
     */
    private boolean isWrapper(Class type) {
        return FastBeanCopier.wrapperMapper.values().contains(type);
    }

    /**
     * 获取对应的基础类型
     * 
     * @param type
     *            指定类
     * @return 基础类型
     */
    protected Class getPrimitiveType(Class type) {
        return FastBeanCopier.wrapperMapper.entrySet().stream().filter(entity -> entity.getValue() == type)
            .map(Map.Entry::getKey).findFirst().orElse(null);
    }

    /**
     * 获取包装类型
     * 
     * @return 包装类型
     */
    protected Class getWrapperType() {
        return FastBeanCopier.wrapperMapper.get(type);
    }

    /**
     * 包装类型转换代码
     * 
     * @param value
     * @return 包装类型。valueOf
     */
    protected String castWrapper(String value) {
        return getWrapperType().getSimpleName().concat(".valueOf(").concat(value).concat(")");
    }

    /**
     * 生成getter 函数
     * 
     * @return getter 函数
     */
    public BiFunction<Class, Class, String> getterFunction() {
        return (targetBeanType, targetType) -> {

            String getterCode = "source." + getReadMethod();
            String generic = "com.lianqi.emcpframework.core.bean.Property.EMPTY_CLASS_ARRAY";
            Field field = ReflectionUtils.findField(targetBeanType, name);
            boolean hasGeneric = false;
            if (field != null) {
                String[] arr =
                    Arrays.stream(ResolvableType.forField(field).getGenerics()).map(ResolvableType::getRawClass)
                        .filter(Objects::nonNull).map(t -> t.getName().concat(".class")).toArray(String[]::new);
                if (arr.length > 0) {
                    generic = "new Class[]{" + String.join(",", arr) + "}";
                    hasGeneric = true;
                }
            }
            String convert = "typeConverter.convert((Object) " + (isPrimitive() ? castWrapper(getterCode) : getterCode)
                + "," + getTypeName(targetType) + ".class," + generic + ")";

            return buildConvertCode(targetType, getterCode, hasGeneric, convert);
        };
    }

    private String buildConvertCode(Class targetType, String getterCode, boolean hasGeneric, String convert) {
        StringBuilder convertCode = new StringBuilder();
        if (targetType != getType()) {
            buildByDifferentType(targetType, getterCode, convert, convertCode);
        } else {
            buildBySameType(targetType, getterCode, hasGeneric, convert, convertCode);
        }
        return convertCode.toString();
    }

    private void buildByDifferentType(Class targetType, String getterCode, String convert, StringBuilder convertCode) {
        if (isPrimitive(targetType)) {
            buildTargetPrimitive(targetType, getterCode, convert, convertCode);
        } else if (isPrimitive()) {
            buildSourcePrimitive(targetType, getterCode, convert, convertCode);
        } else {
            buildTargetType(targetType, convert, convertCode);
        }
    }

    private void buildBySameType(Class targetType, String getterCode, boolean hasGeneric, String convert,
        StringBuilder convertCode) {
        if (Cloneable.class.isAssignableFrom(targetType)) {
            try {
                convertCode.append("(").append(getTypeName()).append(")").append(getterCode).append(".clone()");
            } catch (Exception e) {
                convertCode.append(getterCode);
            }
        } else {
            boolean collectionAndHasGeneric = Collection.class.isAssignableFrom(type) && hasGeneric;
            if (Map.class.isAssignableFrom(targetType) || collectionAndHasGeneric) {
                convertCode.append("(").append(getTypeName()).append(")").append(convert);
            } else {
                convertCode.append("(").append(getTypeName()).append(")").append(getterCode);
            }
        }
    }

    private void buildTargetType(Class targetType, String convert, StringBuilder convertCode) {
        convertCode.append("(").append(getTypeName(targetType)).append(")(").append(convert).append(")");
    }

    private void buildSourcePrimitive(Class targetType, String getterCode, String convert, StringBuilder convertCode) {
        boolean targetIsWarpper = isWrapper(targetType);
        if (targetIsWarpper) {
            convertCode.append(targetType.getName()).append(".valueOf(").append(getterCode).append(")");
        } else {
            convertCode.append("(").append(targetType.getName()).append(")").append(convert);
        }
    }

    private void buildTargetPrimitive(Class targetType, String getterCode, String convert, StringBuilder convertCode) {
        boolean isWarpper = isWrapper();
        Class targetWrapperClass = FastBeanCopier.wrapperMapper.get(targetType);
        Class sourcePrimitive = getPrimitiveType(getType());
        if (isWarpper) {
            convertCode.append(getterCode).append(".").append(sourcePrimitive.getName()).append("Value()");
        } else {
            convertCode.append("((").append(targetWrapperClass.getName()).append(")").append(convert).append(").")
                .append(targetType.getName()).append("Value()");
        }
    }

    /**
     * 生成setter函数
     * 
     * @param settingNameSupplier
     * @return setter函数
     */
    public BiFunction<Class, String, String> setterFunction(Function<String, String> settingNameSupplier) {
        return (sourceType, paramGetter) -> settingNameSupplier.apply(paramGetter);
    }

    /**
     * 生成getter函数 code
     * 
     * @param targetBeanType
     *            目标包装类
     * @param targetType
     *            目标类型
     * @return getter函数 code
     */
    public String generateGetter(Class targetBeanType, Class targetType) {
        return getGetter().apply(targetBeanType, targetType);
    }

    /**
     * 生成setter函数code
     * 
     * @param targetType
     *            目标类型
     * @param getter
     *            获取值code
     * @return setter函数code
     */
    public String generateSetter(Class targetType, String getter) {
        return getSetter().apply(targetType, getter);
    }
}